package pl.edu.mimuw.irs.core.model.design.net;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import pl.edu.mimuw.irs.core.global.IDManager;
import pl.edu.mimuw.irs.core.model.design.net.refinement.IRSAbstractChildren;
import pl.edu.mimuw.irs.core.model.design.net.refinement.IRSNoneChildren;
import pl.edu.mimuw.irs.core.model.design.net.refinement.Refinement;
import pl.edu.mimuw.irs.core.model.petrinet.Arc;
import pl.edu.mimuw.irs.core.model.petrinet.INode;
import pl.edu.mimuw.irs.core.util.Position;

public abstract class IRSAbstractNode<T extends INode> implements IRSNode {

	// *** Statics
	
	private static final long serialVersionUID = 6337784978782507689L;
	
	// *** Attributes

	/** Petri net node mechanism. */
	private T petriNode;
	/** Custom defined position. */
	private Position customPosition;
	/** Parent of this node. */
	private IRSNode parent;
	/** Subtree, based on refinement pattern. */
	private IRSAbstractChildren children;
	/** Is the node expanded, or is it collapsed. This defines whether its children are visible or not. */
	private boolean expanded = false;
	
	/** @return the petriNode */
	protected T getPetriNode() {
		return this.petriNode;
	}
	@Override
	public Position getCustomPosition() {
		return customPosition;
	}
	@Override
	public void setCustomPosition(Position position) {
		this.customPosition = position;
	};
	@Override
	public Refinement getRefinement() {
		if (children == null)
			children = new IRSNoneChildren(this);
		return children.getRefinement();
	}
	@Override
	public void refine(Refinement refinement) {
		if (refinement == null)
			refinement = Refinement.NONE;
		getListeners().firePropertyChange(CHILDREN, getChildren(), this.children = refinement.createFrom(this));
	}
	@Override
	public IRSAbstractChildren getChildren() {
		if (children == null)
			children = new IRSNoneChildren(this);
		return children;
	}
	@Override
	public IRSNode getParent() {
		return parent;
	}
	@Override
	public void setParent(IRSNode parent) {
		this.parent = parent;
	}
	@Override
	public Position getPosition() {
		return this.customPosition == null ? petriNode.getPosition() : this.customPosition; 
	}
	@Override
	public Position getSuperPosition() {
		return petriNode.getPosition();
	}
	@Override
	public boolean isExpanded() {
		return expanded && !getRefinement().equals(Refinement.NONE);
	}
	@Override
	public void setExpanded(boolean expand) {
		boolean oldExpanded = this.expanded;
		this.expanded = expand;
 		for (IRSNode child : getChildren()) {
			child.getListeners().firePropertyChange(VISIBLE, oldExpanded,
					expand);
		}
		getListeners().firePropertyChange(EXPANDED, oldExpanded, expand);
	}


	// *** Constructors

	/** Constructor taking all the necessary parameters that have to be set when the node is being created. */
	public IRSAbstractNode(T node) {
		this.petriNode = node;
	}
	
	/** Copying constructor. */
	@SuppressWarnings("unchecked")
	public IRSAbstractNode(IRSAbstractNode<T> node) {
		this.petriNode = (T) node.petriNode.clone();
		this.children = node.children.clone();
	}
	
	// *** Methods
	
	@Override
	public boolean isRoot() {
		return getParent() == null;
	}
	
	@Override
	public boolean isLeaf() {
		return this.getRefinement().equals(Refinement.NONE);
	}
	
	@Override
	public void connect(IRSNode node) {
		/** At first connect all outgoing nodes from this node. */
		connectAll(Collections.singletonList(node));
		
		/** Connect all outgoing children from this node to all outgoing children of given node. */
		for (IRSNode child : getChildren().getOutputNodes()) {
			child.connect(node);
		}
	}
	
	protected void connectAll(List<IRSNode> nodes) {
		for (IRSNode node : nodes) {
			new IRSArc(getIdManager(), this, node);
			connectAll(node.getChildren().getInputChildren());
		}
	}

	@Override
	public void detach() {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException("Invoked detach on " + this);
	}

	@Override
	public boolean isAncestorOf(IRSNode node) {
		IRSNode parent = node.getParent();
		return (parent != null) && (parent == this || isAncestorOf(parent));
	}

	@Override
	public boolean isDetached() {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException("Invoked isDetached on " + this);
	}

	@Override
	public void addArc(Arc arc) {
		petriNode.addArc(arc);
	}

	@Override
	public void removeArc(Arc arc) {
		petriNode.removeArc(arc);
	}

	@Override
	public Set<Arc> getIncomingArcs() {
		return petriNode.getIncomingArcs();
	}

	@Override
	public String getName() {
		return petriNode.getName();
	}

	@Override
	public Set<Arc> getOutgoingArcs() {
		return petriNode.getOutgoingArcs();
	}

	@Override
	public boolean isConnectedTo(INode node) {
		return petriNode.isConnectedTo(node);
	}

	@Override
	public void setName(String name) {
		petriNode.setName(name);
	}

	@Override
	public void setPosition(Position position) {
		petriNode.setPosition(position);
	}

	@Override
	public String getId() {
		return petriNode.getId();
	}

	@Override
	public IDManager getIdManager() {
		return petriNode.getIdManager();
	}
	
	@Override
	public boolean isVisible() {
		return areAncestorsExpanded(this);
	}
	
	private boolean areAncestorsExpanded(IRSNode node) {
		return node.isRoot() || (node.getParent().isExpanded() && areAncestorsExpanded(node.getParent()));
	}
	
	@Override
	public List<IRSNode> getVisibleNodes() {
		if (!this.isExpanded()) return Collections.singletonList((IRSNode)this);
		List<IRSNode> visibleNodes = new LinkedList<IRSNode>();
		for (IRSNode child : getChildren()) {
			/** TODO : Inefficient? */
			visibleNodes.addAll(child.getVisibleNodes());
		}
		return visibleNodes;
	}

	@Override
	public void addPropertyChangeListener(PropertyChangeListener listener) {
		getListeners().addPropertyChangeListener(listener);
	}

	@Override
	public void addPropertyChangeListener(String property, PropertyChangeListener listener) {
		getListeners().addPropertyChangeListener(property, listener);
	}
	
	@Override
	public void removePropertyChangeListener(PropertyChangeListener listener) {
		getListeners().removePropertyChangeListener(listener);
	}
	
	@Override
	public void removePropertyChangeListener(String property, PropertyChangeListener listener) {
		getListeners().removePropertyChangeListener(property, listener);
	}

	@Override
	public PropertyChangeSupport getListeners() {
		return petriNode.getListeners();
	}
	
	@Override
	public abstract IRSAbstractNode<T> clone();
	
	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof INode)) return false;
		if ((obj instanceof IRSNode)) return obj == this;
		else return obj == petriNode;
	}
	
	@Override
	public int hashCode() {
		return petriNode.hashCode();
	}
}
